﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.IO;
using System.Linq;
using System.Text;
using System.Diagnostics;
using UnityEngine;
using Unity.Mathematics;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
using System.Reflection;

namespace Jinndev {

    public static class CommonUtil {

        public static List<string> ImageExts = new List<string>() {
            ".png", ".jpg", ".jpeg", ".jfif", ".tga", ".dds", ".bmp", ".gif", ".webp"
        };
        public static List<string> ModelExts = new List<string>() {
            ".fbx", ".dae", ".obj", ".xps", ".stl"
        };
        public static List<string> VideoExts = new List<string>() {
            ".mp4", ".webm", ".mkv", ".avi", ".flv", ".rmvb", ".rmb", ".wmv", ".3gp", ".mov", ".m3u8", ".ts"
        };
        public static List<string> CompressExts = new List<string>() {
            ".zip", ".rar", ".rar4", ".7z"
        };
        public static readonly string[] TimeUnit = new string[] { "秒", "分钟", "小时", "天" };

        public static bool IsQuitting { get; private set; }

        [RuntimeInitializeOnLoadMethod]
        public static void InitializeOnLoad() {
            Application.quitting += () => {
                IsQuitting = true;
            };
        }

        public static GameObject Instantiate(string prefabPath, Object parent = null, string path = null) {
            GameObject prefab = Resources.Load<GameObject>(prefabPath);
            if (prefab == null) {
                Debug.LogErrorFormat("Prefab not found: {0}", prefabPath);
                return null;
            }

            return Instantiate(prefab, parent, path);
        }

        public static GameObject Instantiate(GameObject prefab, Object parent = null, string path = null) {
            if (parent == null) {
                return Object.Instantiate(prefab);
            }
            else {
                Transform transform = GetTransform(parent, path);
                if (transform == null) {
                    return Object.Instantiate(prefab);
                }
                else {
                    return Object.Instantiate(prefab, transform, false);
                }
            }
        }

        public static T Instantiate<T>(string prefabPath, Object parent = null, string path = null) where T : Component {
            GameObject obj = Instantiate(prefabPath, parent, path);
            return obj == null ? null : obj.GetComponent<T>();
        }

        public static T Instantiate<T>(GameObject prefab, Object parent = null, string path = null) where T : Component {
            GameObject obj = Instantiate(prefab, parent, path);
            return obj == null ? null : obj.GetComponent<T>();
        }

        public static void QuitGame() {
#if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
#else
                Application.Quit();
#endif
        }

        public static Transform GetTransform(this Object parent, string path) {
            if (parent == null) {
                Debug.LogError($"Parent is null, path: {path}");
                return null;
            }
            bool isSelf = string.IsNullOrEmpty(path);
            Transform transform = null;
            if (parent is Transform) {
                transform = isSelf ? (Transform)parent : ((Transform)parent).Find(path);
            }
            else if (parent is GameObject) {
                transform = isSelf ? ((GameObject)parent).transform : ((GameObject)parent).transform.Find(path);
            }
            else if (parent is Component) {
                transform = isSelf ? ((Component)parent).transform : ((Component)parent).transform.Find(path);
            }
            if (transform == null) {
                Debug.LogFormat("Transform not found, parent: {0}, path: {1}", parent, path);
            }
            return transform;
        }

        public static GameObject GetGameObject(this Object parent, string path) {
            if (parent == null) {
                Debug.LogError($"Parent is null, path: {path}");
                return null;
            }
            bool isSelf = string.IsNullOrEmpty(path);
            if (isSelf) {
                if (parent is GameObject) {
                    return parent as GameObject;
                }
                else if (parent is Transform) {
                    return (parent as Transform).gameObject;
                }
                else if (parent is Component) {
                    return (parent as Component).gameObject;
                }
            }
            Transform transform = GetTransform(parent, path);
            return transform == null ? null : transform.gameObject;
        }

        public static Component GetComponent(this Object parent, string path, Type type) {
            if (string.IsNullOrEmpty(path) && type.IsInstanceOfType(parent)) {
                return parent as Component;
            }
            if (string.IsNullOrEmpty(path) && type.IsInstanceOfType(parent)) {
                return parent as Component;
            }
            Transform transform = GetTransform(parent, path);
            if (transform == null) {
                return null;
            }
            Component com = transform.GetComponent(type);
            if (com == null) {
                Debug.LogFormat("Component not found, parent: {0}, path: {1}, type: {2}", parent, path, type);
                return null;
            }
            return com;
        }

        public static Component GetComponent(this Object parent, string path, string type) {
            Transform transform = GetTransform(parent, path);
            if (transform == null) {
                return null;
            }
            Component com = transform.GetComponent(type);
            if (com == null) {
                Debug.LogFormat("Component not found, parent: {0}, path: {1}, type: {2}", parent, path, type);
                return null;
            }
            return com;
        }

        public static T GetComponent<T>(this Object parent, string path) where T : Component {
            if (string.IsNullOrEmpty(path) && parent is T) {
                return parent as T;
            }
            if (string.IsNullOrEmpty(path) && parent is T) {
                return parent as T;
            }
            Transform transform = GetTransform(parent, path);
            if (transform == null) {
                return null;
            }
            T com = transform.GetComponent<T>();
            if (com == null) {
                Debug.LogFormat("Component not found, parent: {0}, path: {1}, type: {2}", parent, path, typeof(T));
                return null;
            }
            return com;
        }

        public static T AddComponent<T>(this Object parent, string path) where T : Component {
            GameObject gameObject = GetGameObject(parent, path);
            if (gameObject != null) {
                return gameObject.AddComponent<T>();
            }
            return null;
        }

        public static T GetOrAddComponent<T>(this Object parent, string path) where T : Component {
            GameObject gameObject = GetGameObject(parent, path);
            if (gameObject != null) {
                return GetOrAddComponent<T>(gameObject);
            }
            return null;
        }

        public static T GetOrAddComponent<T>(this GameObject gameObject) where T : Component {
            T com = gameObject.GetComponent<T>();
            if (com == null) {
                com = gameObject.AddComponent<T>();
            }
            return com;
        }

        public static T GetOrAddComponent<T>(this Component component) where T : Component {
            T com = component.GetComponent<T>();
            if (com == null) {
                com = component.gameObject.AddComponent<T>();
            }
            return com;
        }

        public static bool RemoveComponent<T>(this Object parent, string path) where T : Component {
            T com = GetComponent<T>(parent, path);
            if (com != null) {
                Object.Destroy(com);
                return true;
            }
            return false;
        }

        public static bool RemoveComponent<T>(this GameObject gameObject) where T : Component {
            T com = gameObject.GetComponent<T>();
            if (com != null) {
                Object.Destroy(com);
                return true;
            }
            return false;
        }

        public static bool RemoveComponent<T>(this Component component) where T : Component {
            T com = component.GetComponent<T>();
            if (com != null) {
                Object.Destroy(com);
                return true;
            }
            return false;
        }

        public static void DestroyChild(this Object parent, string path) {
            Transform transform = GetTransform(parent, path);
            if (transform != null) {
                Object.Destroy(transform.gameObject);
            }
        }

        public static void DestroyChildImmediate(this Object parent, string path) {
            Transform transform = GetTransform(parent, path);
            if (transform != null) {
                Object.DestroyImmediate(transform.gameObject, true);
            }
        }

        public static void DestroyChildren(this Object parent, string path = null) {
            Transform transform = GetTransform(parent, path);
            if (transform != null) {
                if (transform.childCount > 0) {
                    for (int i = transform.childCount - 1; i >= 0; i--) {
                        Object.Destroy(transform.GetChild(i).gameObject);
                    }
                }
            }
        }

        public static void DestroyChildrenImmediate(this Object parent, string path = null) {
            Transform transform = GetTransform(parent, path);
            if (transform != null) {
                if (transform.childCount > 0) {
                    for (int i = transform.childCount - 1; i >= 0; i--) {
                        Object.DestroyImmediate(transform.GetChild(i).gameObject, true);
                    }
                }
            }
        }

        public static Transform[] GetChildren(this Object parent, string path = null) {
            Transform transform = GetTransform(parent, path);
            if (transform != null) {
                Transform[] children = new Transform[transform.childCount];
                for (int i = 0; i < transform.childCount; i++) {
                    children[i] = transform.GetChild(i);
                }
                return children;
            }
            return new Transform[0];
        }

        public static RectTransform[] GetRectChildren(this Object parent, string path = null) {
            Transform transform = GetTransform(parent, path);
            if (transform != null) {
                RectTransform[] children = new RectTransform[transform.childCount];
                for (int i = 0; i < transform.childCount; i++) {
                    children[i] = transform.GetChild(i) as RectTransform;
                }
                return children;
            }
            return new RectTransform[0];
        }

        public static void SetLayer(GameObject gameObject, int layer, bool withChildren) {
            gameObject.layer = layer;
            if (withChildren) {
                Transform transform = gameObject.transform;
                for (int i = 0; i < transform.childCount; i++) {
                    SetLayer(transform.GetChild(i).gameObject, layer, withChildren);
                }
            }
        }

        public static void SetActive(Object parent, string path, bool active) {
            GameObject obj = GetGameObject(parent, path);
            if (obj != null) {
                obj.SetActive(active);
            }
        }

        public static bool ActiveSelf(Object parent, string path) {
            GameObject obj = GetGameObject(parent, path);
            if (obj != null) {
                return obj.activeSelf;
            }
            return false;
        }

        public static bool ToggleActive(Object parent, string path = null) {
            GameObject obj = GetGameObject(parent, path);
            if (obj != null) {
                obj.SetActive(!obj.activeSelf);
                return obj.activeSelf;
            }
            return false;
        }

        public static void SetRendererColor(Object parent, string path, Color color) {
            Renderer com = GetComponent<Renderer>(parent, path);
            if (com != null) {
                com.material.color = color;
            }
        }

        public static void SetMainTexture(Material material, Texture2D mainTexture, string key = null) {
            if (string.IsNullOrWhiteSpace(key)) {
                material.mainTexture = mainTexture; // [MainTexture]
            }
            else {
                material.SetTexture(key, mainTexture);
            }
            //material.SetTexture("_MainTex", mainTexture);
            //material.SetTexture("_BaseMap", mainTexture);
        }

        public static void SetMainColor(Material material, Color color, string key = null) {
            if (string.IsNullOrWhiteSpace(key)) {
                material.color = color; // [MainColor]
            }
            else {
                material.SetColor(key, color);
            }
        }

        public static bool LoadImage(string file, Texture2D texture) {
            if (!File.Exists(file)) {
                return false;
            }
            byte[] data = File.ReadAllBytes(file);
            return texture.LoadImage(data, false);
        }

        public static bool Approximately(float a, float b, float maxDelta) {
            return Mathf.Abs(a - b) <= maxDelta;
        }

        public static bool Approximately(Vector3 a, Vector3 b, float maxDistance) {
            return Approximately(Vector3.Distance(a, b), 0, maxDistance);
        }

        public static long GetTimeSeconds(DateTime dateTime) {
            DateTime dtFrom = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return (dateTime.Ticks - dtFrom.Ticks) / 10000000;
        }

        public static long GetTimeMillis(DateTime dateTime) {
            DateTime dtFrom = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return (dateTime.Ticks - dtFrom.Ticks) / 10000;
        }

        public static string GetTimeStamp() {
            return DateTime.Now.ToString("HH:mm:ss");
        }

        public static string GetDateStamp() {
            return DateTime.Now.ToString("yyyy-MM-dd");
        }

        public static string GetDateTimeStamp() {
            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        }

        public static string GetAgoTimeStamp(DateTime dateTime) {
            TimeSpan timeSpan = DateTime.Now - dateTime;
            if (timeSpan.TotalSeconds < 1) {
                return "刚刚";
            }
            else if (timeSpan.TotalSeconds < 60) {
                return $"{timeSpan.TotalSeconds:0}秒前";
            }
            else if (timeSpan.TotalMinutes < 60) {
                return $"{timeSpan.TotalMinutes:0}分钟前";
            }
            else if (timeSpan.TotalHours < 24) {
                return $"{timeSpan.TotalHours:0}小时前";
            }
            else if (timeSpan.TotalDays < 7) {
                return $"{timeSpan.TotalDays:0}天前";
            }
            else {
                return dateTime.ToString("yyyy-MM-dd");
            }
        }

        public static DateTime GetUTCTimeBySeconds(double seconds) {
            return new DateTime(1970, 1, 1).AddSeconds(seconds);
        }

        public static DateTime GetUTCTimeByMilliseconds(double milliseconds) {
            return new DateTime(1970, 1, 1).AddMilliseconds(milliseconds);
        }

        public static DateTime GetTimeBySeconds(double seconds) {
            return new DateTime(1970, 1, 1).AddSeconds(seconds).ToLocalTime();
        }

        public static DateTime GetTimeByMilliseconds(double milliseconds) {
            return new DateTime(1970, 1, 1).AddMilliseconds(milliseconds).ToLocalTime();
        }

        public static float4 ApplyAtlasTilingOffset(float4 tilingOffset, float4 atlasTilingOffset) {
            tilingOffset.xy *= atlasTilingOffset.xy;
            tilingOffset.zw = atlasTilingOffset.zw + tilingOffset.zw * atlasTilingOffset.xy;
            return tilingOffset;
        }

        public static string SubStringFrom(string str, string from) {
            return SubStringFromTo(str, from, null);
        }

        public static string SubStringTo(string str, string to) {
            return SubStringFromTo(str, null, to);
        }

        public static string SubStringTo(string str, string[] toArray) {
            int minIndex = -1;

            for (int i = 0; i < toArray.Length; i++) {
                int index = str.IndexOf(toArray[i]);
                if (index != -1) {
                    minIndex = minIndex == -1 ? index : Mathf.Min(index, minIndex);
                }
            }

            return minIndex == -1 ? str : str.Substring(0, minIndex);
        }

        public static string SubStringFromTo(string str, string from, string to) {
            if (string.IsNullOrEmpty(str)) {
                return str;
            }
            if (from != null && str.IndexOf(from) >= 0) {
                str = str.Substring(str.IndexOf(from) + from.Length);
            }
            if (to != null && str.IndexOf(to) >= 0) {
                str = str.Substring(0, str.IndexOf(to));
            }
            return str;
        }

        public static string SubStringFromLast(string str, string from) {
            return SubStringFromLastTo(str, from, null);
        }

        public static string SubStringFromLastTo(string str, string from, string to) {
            if (string.IsNullOrEmpty(str)) {
                return str;
            }
            if (from != null && str.LastIndexOf(from) >= 0) {
                str = str.Substring(str.LastIndexOf(from) + from.Length);
            }
            if (to != null && str.IndexOf(to) >= 0) {
                str = str.Substring(0, str.IndexOf(to));
            }
            return str;
        }

        public static string SubStringToLast(string str, string to) {
            if (string.IsNullOrEmpty(str)) {
                return str;
            }
            if (to != null && str.LastIndexOf(to) >= 0) {
                str = str.Substring(0, str.LastIndexOf(to));
            }
            return str;
        }

        public static string SubStringFromToLast(string str, string from, string to) {
            if (string.IsNullOrEmpty(str)) {
                return str;
            }
            if (from != null && str.IndexOf(from) >= 0) {
                str = str.Substring(str.IndexOf(from) + from.Length);
            }
            if (to != null && str.LastIndexOf(to) >= 0) {
                str = str.Substring(0, str.LastIndexOf(to));
            }
            return str;
        }

        public static string GetMD5(string myString) {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] fromData = Encoding.Unicode.GetBytes(myString);
            byte[] targetData = md5.ComputeHash(fromData);
            string byte2String = null;

            for (int i = 0; i < targetData.Length; i++) {
                byte2String += targetData[i].ToString("x");
            }

            return byte2String;
        }

        public static string GetFileMD5(string filePath) {
            if (!File.Exists(filePath)) {
                return null;
            }
            MD5CryptoServiceProvider md5csp = new MD5CryptoServiceProvider();
            byte[] bytes = null;
            using (FileStream stream = File.OpenRead(filePath)) {
                bytes = md5csp.ComputeHash(stream);
            }

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++) {
                sb.Append(bytes[i].ToString("x2"));
            }
            return sb.ToString();
        }

        public static long GetFileSize(string path) {
            return new FileInfo(path).Length;
        }

        public static string GetFileSizeDesc(string path) {
            return GetFileSizeDesc(GetFileSize(path));
        }

        public static string GetFileSizeDesc(double size) {
            string sign = "";
            if (size < 0) {
                size = -size;
                sign = "-";
            }

            if (size < 1024) {
                return size + "B";
            }

            size /= 1024;
            if (size < 1024) {
                return string.Format(sign + "{0:F2}KB", size);
            }

            size /= 1024;
            if (size < 1024) {
                return string.Format(sign + "{0:F2}MB", size);
            }

            size /= 1024;
            if (size < 1024) {
                return string.Format(sign + "{0:F2}GB", size);
            }

            size /= 1024;
            if (size < 1024) {
                return string.Format(sign + "{0:F2}TB", size);
            }

            size /= 1024;
            return string.Format(sign + "{0:F2}PB", size);
        }

        public static string GetShortFileName(string filePath) {
            string dir = Path.GetDirectoryName(filePath);
            string fileName = Path.GetFileName(filePath);
            return Path.Combine(Path.GetFileName(dir), fileName);
        }

        public static string GetValidFileName(string fileName) {
            foreach (char c in Path.GetInvalidFileNameChars()) {
                fileName = fileName.Replace(c.ToString(), string.Empty);
            }
            while (fileName.EndsWith(".")) {
                fileName = fileName.Substring(0, fileName.Length - 1);
            }
            return fileName;
        }

        public static void OpenDir(string dir) {
            if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir)) {
                System.Diagnostics.Process.Start(dir);
            }
        }

        public static string GetRuntimeRootPath(params string[] subPaths) {
            string root = new DirectoryInfo(Application.dataPath).Parent.FullName;
            if (subPaths != null && subPaths.Length > 0) {
                return Path.Combine(root, Path.Combine(subPaths));
            }
            return root;
        }

        public static int CompareVersion(string verA, string verB) {
            string[] arrayA = verA.Split('.');
            string[] arrayB = verB.Split('.');

            int max = Mathf.Max(arrayA.Length, arrayB.Length);

            for (int i = 0; i < max; i++) {
                string a = i < arrayA.Length ? arrayA[i] : "0";
                string b = i < arrayB.Length ? arrayB[i] : "0";
                if (a != b) {
                    return a.CompareTo(b);
                }
            }
            return 0;
        }

        public static string GetTimeDuration(long seconds, string[] units) {
            if (seconds < 60) {
                return $"{seconds}{units[0]}";
            }

            long minutes = seconds / 60;
            seconds %= 60;
            if (minutes < 60) {
                return $"{minutes}{units[1]}{seconds}{units[0]}";
            }

            long hours = minutes / 60;
            minutes %= 60;
            if (hours < 60) {
                return $"{hours}{units[2]}{minutes}{units[1]}{seconds}{units[0]}";
            }

            long days = hours / 24;
            hours %= 24;
            return $"{days}{units[3]}{hours}{units[2]}{minutes}{units[1]}分钟{seconds}{units[0]}";
        }

        public static string GetTimeDuration(double seconds) {
            return TimeSpan.FromSeconds(seconds).ToString(@"hh\:mm\:ss");
        }

        /// <summary>
        /// 00:00:00格式
        /// </summary>
        public static long GetSecondsByTime(string time) {
            if (time == null) {
                return 0;
            }
            time = SubStringTo(time, ".");

            string[] array = time.Split(':');
            long seconds = 0;

            for (int i = 0; i < array.Length; i++) {
                int revese = array.Length - 1 - i;

                if (long.TryParse(array[revese], out long num)) {
                    if (i == 0) {
                        seconds += num;
                    }
                    if (i == 1) {
                        seconds += num * 60;
                    }
                    else if (i == 2) {
                        seconds += num * 60 * 60;
                    }
                }
            }
            return seconds;
        }

        public static bool FitAtlas(Vector2[] sizes, int padding, int atlasSize, List<Rect> results) {
            if (Texture2D.GenerateAtlas(sizes, padding, atlasSize, results)) {
                foreach (var rect in results) {
                    if (rect == Rect.zero) {
                        return false;
                    }
                }
                return true;
            }
            else {
                return false;
            }
        }

        public static bool IsImageFile(string fileName) {
            string ext = Path.GetExtension(fileName).ToLower();
            return ImageExts.Contains(ext);
        }

        public static bool IsVideoFile(string fileName) {
            string ext = Path.GetExtension(fileName).ToLower();
            return VideoExts.Contains(ext);
        }

        public static bool IsModelFile(string fileName) {
            string ext = Path.GetExtension(fileName).ToLower();
            return ModelExts.Contains(ext);
        }

        public static bool IsCompressFile(string fileName) {
            string ext = Path.GetExtension(fileName).ToLower();
            return CompressExts.Contains(ext);
        }

        public static bool IsMatchExtensions(string fileName, params string[] exts) {
            string ext = Path.GetExtension(fileName).ToLower();
            return exts.Contains(ext);
        }

        public static void PrepareFileDir(string file) {
            string dir = Path.GetDirectoryName(file);
            if (!Directory.Exists(dir)) {
                Directory.CreateDirectory(dir);
            }
        }

        public static void PrepareDir(string dir) {
            if (!Directory.Exists(dir)) {
                Directory.CreateDirectory(dir);
            }
        }

        public static void CreateDirectories(params string[] dirs) {
            foreach (string dir in dirs) {
                if (!Directory.Exists(dir)) {
                    Directory.CreateDirectory(dir);
                }
            }
        }

        /// <summary>
        /// 把目录移动到新目录，可以指定新目录名
        /// </summary>
        /// <param name="dirPath"></param>
        /// <param name="targetDir"></param>
        /// <param name="newDirName"></param>
        /// <param name="overrideExist"></param>
        public static void MoveDirToDir(string dirPath, string targetDir, string newDirName = null, bool overrideExist = false) {
            if (!Directory.Exists(dirPath)) {
                return;
            }

            string newDir = null;
            if (string.IsNullOrWhiteSpace(newDirName)) {
                newDir = Path.Combine(targetDir, Path.GetFileName(dirPath));
            }
            else {
                newDir = Path.Combine(targetDir, Path.GetFileName(newDirName));
            }

            if (Directory.Exists(newDir)) {
                if (overrideExist) {
                    Debug.Log("Delete \"" + Path.GetFileName(newDir) + "\"");
                    Directory.Delete(newDir, true);
                }
                else {
                    return;
                }
            }
            Directory.Move(dirPath, newDir);
        }

        public static void MoveFileToDir(string filePath, string targetDir, string newFileName = null, bool overrideExist = false) {
            if (!File.Exists(filePath)) {
                return;
            }

            string newFile;
            if (string.IsNullOrWhiteSpace(newFileName)) {
                newFile = Path.Combine(targetDir, Path.GetFileName(filePath));
            }
            else {
                newFile = Path.Combine(targetDir, Path.GetFileName(newFileName));
            }

            if (File.Exists(newFile)) {
                if (overrideExist) {
                    File.Delete(newFile);
                }
                else {
                    return;
                }
            }
            File.Move(filePath, newFile);
        }

        public static void MoveFilesAndDirs(string fromDir, string toDir) {
            if (!Directory.Exists(toDir)) {
                Directory.CreateDirectory(toDir);
            }
            string[] dirs = Directory.GetDirectories(fromDir);
            string[] files = Directory.GetFiles(fromDir);

            for (int i = 0; i < dirs.Length; i++) {
                string name = Path.GetFileName(dirs[i]);
#if UNITY_EDITOR
                if (UnityEditor.EditorUtility.DisplayCancelableProgressBar($"Move Dirs {i}/{dirs.Length}", dirs[i], (float)i / dirs.Length)) {
                    break;
                }
#endif
                Directory.Move(dirs[i], Path.Combine(toDir, name));
            }
            for (int i = 0; i < files.Length; i++) {
                string name = Path.GetFileName(files[i]);
#if UNITY_EDITOR
                if (UnityEditor.EditorUtility.DisplayCancelableProgressBar($"Move Files {i}/{files.Length}", files[i], (float)i / files.Length)) {
                    break;
                }
#endif
                File.Move(files[i], Path.Combine(toDir, name));
            }
#if UNITY_EDITOR
            UnityEditor.EditorUtility.ClearProgressBar();
#endif
        }

        public static void CopyFiles(string fromDir, string toDir) {
            string[] files = Directory.GetFiles(fromDir, "*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++) {
                string file = files[i];
#if UNITY_EDITOR
                if (UnityEditor.EditorUtility.DisplayCancelableProgressBar($"Copy Files {i}/{files.Length}", file, (float)i / files.Length)) {
                    break;
                }
#endif
                string filePath = file.Substring(fromDir.Length + 1);
                string targetFile = Path.Combine(toDir, filePath);

                FileInfo targetInfo = new FileInfo(targetFile);
                if (!targetInfo.Directory.Exists) {
                    targetInfo.Directory.Create();
                }
                File.Copy(file, targetFile, true);
            }
#if UNITY_EDITOR
            UnityEditor.EditorUtility.ClearProgressBar();
#endif
        }

        public static bool FileExists(string path) {
            return !string.IsNullOrEmpty(path) && File.Exists(path);
        }

        public static bool DirectoryExists(string path) {
            return !string.IsNullOrEmpty(path) && Directory.Exists(path);
        }

        public static string GetNonRepeatingFilePath(string filePath) {
            if (!File.Exists(filePath)) {
                return filePath;
            }
            string dir = Path.GetDirectoryName(filePath);
            string fileName = Path.GetFileNameWithoutExtension(filePath);
            string ext = Path.GetExtension(filePath);

            fileName = CommonUtil.SubStringToLast(fileName, "~");

            int num = 1;
            string newFile = Path.Combine(dir, fileName + "~" + num + ext);

            while (File.Exists(newFile)) {
                num++;
                newFile = Path.Combine(dir, fileName + "~" + num + ext);
            }
            return newFile;
        }

        public static ProcessStartInfo CreateProcessStartInfo(string fileName, string args, Encoding encoding = null, bool redirectStandardOutput = false, bool redirectStandardError = false) {
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = fileName;
            startInfo.Arguments = args;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            //startInfo.WorkingDirectory = srcDir;
            startInfo.RedirectStandardInput = false;
            startInfo.RedirectStandardOutput = redirectStandardOutput;
            startInfo.RedirectStandardError = redirectStandardError;
            if (redirectStandardOutput) {
                startInfo.StandardOutputEncoding = encoding ?? Encoding.UTF8;
            }
            if (redirectStandardError) {
                startInfo.StandardErrorEncoding = encoding ?? Encoding.UTF8;
            }
            return startInfo;
        }

        public static void StartProcess(string fileName, string args, Encoding encoding, Action<string> onOutput, Action<string> onError, bool consumeOutput = true) {
            StartProcess(fileName, args, encoding, onOutput, onError, consumeOutput, out _);
        }

        public static void StartProcess(string fileName, string args, Encoding encoding, Action<string> onOutput, Action<string> onError, out Process process) {
            StartProcess(fileName, args, encoding, onOutput, onError, true, out process);
        }

        public static void StartProcess(string fileName, string args, Encoding encoding, Action<string> onOutput, Action<string> onError, bool consumeOutput, out Process process) {
            ProcessStartInfo startInfo = CreateProcessStartInfo(fileName, args, encoding, onOutput != null, onError != null);
            StartProcess(startInfo, onOutput, onError, consumeOutput, out process);
        }

        public static void StartProcess(ProcessStartInfo startInfo, Action<string> onOutput, Action<string> onError, bool consumeOutput, out Process process) {
            process = new Process();
            process.StartInfo = startInfo;

            if (!consumeOutput) {
                if (onOutput != null) {
                    process.OutputDataReceived += (sender, e) => {
                        onOutput(e.Data);
                    };
                }
                if (onError != null) {
                    process.ErrorDataReceived += (sender, e) => {
                        onError(e.Data);
                    };
                }
            }

            if (process.Start()) {
                if (consumeOutput) {
                    if (onOutput != null) {
                        ConsumeOutput(process.StandardOutput, onOutput);
                    }
                    if (onError != null) {
                        ConsumeOutput(process.StandardError, onError);
                    }
                }

                process.WaitForExit();
                //if (onOutput != null) {
                //    process.StandardOutput.BaseStream.Flush();
                //}
                //if (onError != null) {
                //    process.StandardError.BaseStream.Flush();
                //}
                process.Dispose();
                //Debug.Log("Process complete");
            }
            else {
                Debug.LogError("Start process fail");
            }
        }

        public static void StartProcess(string fileName, string args, Encoding encoding, DataReceivedEventHandler onOutput, DataReceivedEventHandler onError, out Process process) {
            //Debug.Log(fileName + " " + args);
            ProcessStartInfo startInfo = CreateProcessStartInfo(fileName, args, encoding, onOutput != null, onError != null);
            StartProcess(startInfo, onOutput, onError, out process);
        }

        public static void StartProcess(ProcessStartInfo startInfo, DataReceivedEventHandler onOutput, DataReceivedEventHandler onError, out Process process) {
            process = new Process();
            process.StartInfo = startInfo;

            if (onOutput != null) {
                process.OutputDataReceived += onOutput;
            }
            if (onError != null) {
                process.ErrorDataReceived += onError;
            }

            if (process.Start()) {
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
                process.Dispose();
                Debug.Log("Process complete");
            }
            else {
                Debug.LogError("Start process fail");
            }
        }

        static async void ConsumeOutput(TextReader reader, Action<string> callback) {
            char[] buffer = new char[256];
            int cch;

            while ((cch = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0) {
                callback(new string(buffer, 0, cch));
            }
        }

        public static void Add<K, V>(Dictionary<K, List<V>> dict, K key, V value) {
            List<V> list;
            if (dict.TryGetValue(key, out list)) {
                if (list == null) {
                    list = new List<V>();
                    dict[key] = list;
                }
                list.Add(value);
            }
            else {
                list = new List<V>();
                list.Add(value);
                dict.Add(key, list);
            }
        }

        public static bool Remove<K, V>(Dictionary<K, List<V>> dict, K key, V value) {
            List<V> list;
            if (dict.TryGetValue(key, out list)) {
                return list.Remove(value);
            }
            return false;
        }

        public static Rect TilingOffsetToRect(float4 tilingOffset) {
            return new Rect(tilingOffset.z, tilingOffset.w, tilingOffset.x, tilingOffset.y);
        }

        public static T GetAttribute<T>(ICustomAttributeProvider provider, bool inherit) where T : Attribute {
            T[] attrs = (T[])provider.GetCustomAttributes(typeof(T), inherit);
            return attrs.Length > 0 ? attrs[0] : null;
        }

        public static T[] GetAttributes<T>(ICustomAttributeProvider provider, bool inherit) where T : Attribute {
            return (T[])provider.GetCustomAttributes(typeof(T), inherit);
        }

        public static Type[] GetTypesByBaseType(AppDomain appDomain, Type baseType) {
            return appDomain.GetAssemblies()
                        .SelectMany(a => a.GetTypes().Where(t => t.BaseType == baseType))
                        .ToArray();
        }

        public static Type GetTypeByBaseType(AppDomain appDomain, Type interfaceType) {
            Type[] types = GetTypesByBaseType(appDomain, interfaceType);
            return types.Length == 0 ? null : types[0];
        }

        public static Type[] GetTypesByParentType(AppDomain appDomain, Type parentType) {
            return appDomain.GetAssemblies()
                        .SelectMany(a => a.GetTypes().Where(t => t.IsSubclassOf(parentType)))
                        .ToArray();
        }

        public static Type GetTypeByParentType(AppDomain appDomain, Type interfaceType) {
            Type[] types = GetTypesByParentType(appDomain, interfaceType);
            return types.Length == 0 ? null : types[0];
        }

        public static Type[] GetTypesByInterface(AppDomain appDomain, Type interfaceType) {
            return appDomain.GetAssemblies()
                        .SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(interfaceType)))
                        .ToArray();
        }

        public static Type GetTypeByInterface(AppDomain appDomain, Type interfaceType) {
            Type[] types = GetTypesByInterface(appDomain, interfaceType);
            return types.Length == 0 ? null : types[0];
        }

        public static string[] SplitTrim(this string str, char separator) {
            string[] array = str.Split(new char[] { separator });
            for (int i = 0; i < array.Length; i++) {
                array[i] = array[i].Trim();
            }
            return array;
        }

        public static string[] SplitTrim(this string str, char separator, int count) {
            string[] array = str.Split(new char[] { separator }, count);
            for (int i = 0; i < array.Length; i++) {
                array[i] = array[i].Trim();
            }
            return array;
        }

        public static string[] SplitTrim(this string str, string separator) {
            string[] array = str.Split(new string[] { separator }, StringSplitOptions.None);
            for (int i = 0; i < array.Length; i++) {
                array[i] = array[i].Trim();
            }
            return array;
        }

        public static string[] SplitTrim(this string str, string separator, int count) {
            string[] array = str.Split(new string[] { separator }, count, StringSplitOptions.None);
            for (int i = 0; i < array.Length; i++) {
                array[i] = array[i].Trim();
            }
            return array;
        }

        public static string RandomString(int len) {
            string chars = "abcdefhijkmnprstwxyz0123456789";
            int maxPos = chars.Length;
            StringBuilder pwd = new StringBuilder();
            for (int i = 0; i < len; i++) {
                pwd.Append(chars[UnityEngine.Random.Range(0, maxPos)]);
            }
            return pwd.ToString();
        }

        public static T RandomList<T>(IList<T> list) {
            if (list == null || list.Count == 0) {
                return default;
            }
            int index = UnityEngine.Random.Range(0, list.Count);
            return list[index];
        }

        public static TKey RandomDictKey<TKey, TValue>(IDictionary<TKey, TValue> dict) {
            if (dict == null || dict.Count == 0) {
                return default;
            }
            TKey[] array = new TKey[dict.Count];
            dict.Keys.CopyTo(array, 0);
            int index = UnityEngine.Random.Range(0, array.Length);
            return array[index];
        }

        public static TValue RandomDictValue<TKey, TValue>(IDictionary<TKey, TValue> dict) {
            if (dict == null || dict.Count == 0) {
                return default;
            }
            TValue[] array = new TValue[dict.Count];
            dict.Values.CopyTo(array, 0);
            int index = UnityEngine.Random.Range(0, array.Length);
            return array[index];
        }

        public static T RandomArray<T>(params T[] array) {
            if (array == null || array.Length == 0) {
                return default;
            }
            int index = UnityEngine.Random.Range(0, array.Length);
            return array[index];
        }

        public static bool RandomProbability(float probability) {
            if (probability <= 0) {
                return false;
            }
            if (probability >= 1) {
                return true;
            }
            return UnityEngine.Random.Range(0, (int)(1 / probability)) < 1;
        }

        public static int RandomSign() {
            return RandomProbability(0.5f) ? 1 : -1;
        }

        public static bool RandomBool() {
            return RandomProbability(0.5f) ? true : false;
        }

        public static bool OutOfRange(float3 position, float4 viewBounds) {
            return position.x < viewBounds.x || position.y < viewBounds.y || position.x > viewBounds.z || position.y > viewBounds.w;
        }

        public static bool InOfRange(float3 position, float4 viewBounds) {
            return position.x >= viewBounds.x && position.y >= viewBounds.y && position.x <= viewBounds.z && position.y <= viewBounds.w;
        }

        public static void ToGray(Texture2D input, Texture2D output, int method = 1) {
            if (!input.isReadable || !output.isReadable) {
                Debug.LogError("Texture isn't readable");
                return;
            }

            Color[] colors = input.GetPixels();
            for (int i = 0; i < input.width * input.height; i++) {
#if UNITY_EDITOR
                if (i % 32 == 0) {
                    UnityEditor.EditorUtility.DisplayProgressBar("ToGray", $"{(i + 1)} / {(input.width * input.height)}", (float)(i + 1) / (input.width * input.height));
                }
#endif
                colors[i] = ToGray(colors[i], method);
            }
            output.SetPixels(colors);
#if UNITY_EDITOR
            UnityEditor.EditorUtility.ClearProgressBar();
#endif
        }

        public static Color ToGray(Color color, int method = 1) {
            if (method == 1) {
                float g = color.r * 0.299f + color.g * 0.587f + color.b * 0.114f;
                return new Color(g, g, g, color.a);
            }
            else if (method == 2) {
                float g = (color.r + color.g + color.b) / 3;
                return new Color(g, g, g, color.a);
            }
            else if (method == 3) {
                float g = Mathf.Max(color.r + color.g + color.b);
                return new Color(g, g, g, color.a);
            }
            return color;
        }

        public static Color ColorHexToColor(string hex) {
            float[] rgba = ColorHexToRGBA(hex);
            return new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
        }

        public static float[] ColorHexToRGBA(string hex) {
            float r = 1, g = 1, b = 1, a = 1;
            char[] chars = hex.StartsWith("#") ? hex.ToCharArray(1, hex.Length - 1) : hex.ToCharArray();
            if (chars.Length == 3 || chars.Length == 4) {
                r = Convert.ToInt32(chars[0].ToString(), 16) / 15f;
                g = Convert.ToInt32(chars[1].ToString(), 16) / 15f;
                b = Convert.ToInt32(chars[2].ToString(), 16) / 15f;
                if (chars.Length == 4) {
                    a = Convert.ToInt32(chars[3].ToString(), 16) / 15f;
                }
            }
            if (chars.Length == 6 || chars.Length == 8) {
                r = Convert.ToInt32(chars[0].ToString() + chars[1].ToString(), 16) / 255f;
                g = Convert.ToInt32(chars[2].ToString() + chars[3].ToString(), 16) / 255f;
                b = Convert.ToInt32(chars[4].ToString() + chars[5].ToString(), 16) / 255f;
                if (chars.Length == 8) {
                    a = Convert.ToInt32(chars[6].ToString() + chars[7].ToString(), 16) / 255f;
                }
            }
            return new float[4] { r, g, b, a };
        }

        public static string ColorToHex(Color color) {
            return ColorRGBAToHex(color.r, color.g, color.b, color.a);
        }

        public static string ColorRGBAToHex(float r, float g, float b, float a) {
            StringBuilder sb = new StringBuilder(9);
            sb.Append("#");
            float[] array = new float[4] { r, g, b, a };
            for (int i = 0; i < 4; i++) {
                string s = Convert.ToString((int)(array[i] * 255), 16);
                if (s.Length == 1) {
                    sb.Append(0);
                }
                sb.Append(s);
            }
            return sb.ToString();
        }

        public static float4 GetViewBounds(Camera camera, Vector2 viewBuffer) {
            float3 leftBottom = camera.ScreenToWorldPoint(Vector2.zero - viewBuffer);
            float3 rightTop = camera.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height) + viewBuffer);
            return new float4(leftBottom.xy, rightTop.xy);
        }

        /// <summary>
        /// 替换文件名.扩展名
        /// </summary>
        public static string ReplaceFileName(string file, string nameWithExt) {
            return Path.Combine(Path.GetDirectoryName(file), nameWithExt);
        }

        /// <summary>
        /// 仅替换文件名，保留扩展名
        /// </summary>
        public static string ReplaceFileNameWithoutExt(string file, string nameWithoutExt) {
            string ext = Path.GetExtension(file).ToLower();
            return Path.Combine(Path.GetDirectoryName(file), nameWithoutExt + ext);
        }

        public static string FindFirstFileByExt(string dir, string ext, SearchOption searchOption = SearchOption.TopDirectoryOnly) {
            string[] files = Directory.GetFiles(dir, "*." + ext, searchOption);
            return files.Length == 0 ? null : files[0];
        }

        /// <summary>
        /// 根据canvas模式、缩放比例，得到实际屏幕坐标，屏幕左下角为(0, 0)
        /// </summary>
        public static Vector2 GetScreenPosition(RectTransform rectTransform, Canvas canvas, float uiScale) {
            Vector2 screenPoint = rectTransform.position;
            if (canvas.worldCamera != null) {
                screenPoint = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, screenPoint);
            }
            return screenPoint * uiScale;
        }

        public static void CopyTransform(Transform from, Transform to, bool withScale = true) {
            to.position = from.position;
            to.rotation = from.rotation;
            if (withScale) {
                to.localScale = from.localScale;
            }
        }

        public static void CopyTransform(Object from, Object to, bool withScale = true) {
            CopyTransform(from.GetTransform(null), to.GetTransform(null), withScale);
        }

        /// <summary>
        /// 朝向目标方向转动
        /// </summary>
        /// <param name="speed">每秒转动的最大角度</param>
        public static void RotateTowards(Transform transform, Vector3 direction, float speed, float deltaTime) {
            if (!direction.Equals(Vector3.zero)) {
                transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(direction), speed * deltaTime);
            }
        }

        /// <summary>
        /// 旋转方向到指定的角度
        /// </summary>
        public static Vector3 RotateDirectionByAngle(Vector3 direction, float angle) {
            return RotateDirectionByAngle(direction, angle, Vector3.up);
        }

        /// <summary>
        /// 旋转方向到指定的角度
        /// </summary>
        public static Vector3 RotateDirectionByAngle(Vector3 direction, float angle, Vector3 axis) {
            return Quaternion.AngleAxis(angle, axis) * direction;
        }

        /// <summary>
        /// 根据设计分辨率，得到UI屏幕实际大小
        /// </summary>
        /// <returns></returns>
        public static Vector2 GetUIScreenSize(Vector2 referenceResolution) {
            float uiScale = referenceResolution.x / Screen.width;
            return new Vector2(Screen.width, Screen.height) * uiScale;
        }

        /// <summary>
        /// 根据屏幕坐标，获得UI锚点坐标
        /// </summary>
        public static Vector2 ScreenPointToAnchoredPosition(Vector2 screenPoint, float uiScale) {
            return (screenPoint - new Vector2(Screen.width, Screen.height) * 0.5f) * uiScale;
        }

        public static Vector2 WorldPointToAnchoredPosition(Camera camera, Vector3 worldPosition, float uiScale) {
            Vector3 pos = camera.WorldToScreenPoint(worldPosition);
            return ScreenPointToAnchoredPosition(pos, uiScale);
        }

        public static Vector2Int LimitResolution(int width, int height, int maxWidth, int maxHeight) {
            if (width <= maxWidth && height <= maxHeight) {
                return new Vector2Int(width, height);
            }
            float ratio = (float)width / height;
            float maxRatio = (float)maxWidth / maxHeight;
            if (maxRatio > ratio) {
                width = width * maxHeight / height;
                height = maxHeight;
            }
            else {
                height = height * maxWidth / width;
                width = maxWidth;
            }
            return new Vector2Int(width, height);
        }

        public static bool ToggleParticle(bool isOn, bool isPlaying, ParticleSystem[] particles, params ParticleSystem[] extraParticles) {
            if (isOn && !isPlaying) {
                isPlaying = true;
                foreach (var particle in particles) {
                    particle.Play();
                }
                foreach (var particle in extraParticles) {
                    particle.Play();
                }
            }
            else if (!isOn && isPlaying) {
                isPlaying = false;
                foreach (var particle in particles) {
                    particle.Stop();
                }
                foreach (var particle in extraParticles) {
                    particle.Stop();
                }
            }
            return isPlaying;
        }



        public static bool ToggleParticle(bool play, bool isPlaying, params ParticleSystem[] particles) {
            if (play && !isPlaying) {
                isPlaying = true;
                foreach (var particle in particles) {
                    particle.Play();
                }
            }
            else if (!play && isPlaying) {
                isPlaying = false;
                foreach (var particle in particles) {
                    particle.Stop();
                }
            }
            return isPlaying;
        }

        public static bool ToggleAudio(bool play, bool isPlaying, AudioSource[] audioSources, params AudioSource[] extraAudioSources) {
            if (play && !isPlaying) {
                isPlaying = true;
                foreach (var audioSource in audioSources) {
                    if (audioSource.enabled && audioSource.gameObject.activeInHierarchy) {
                        audioSource.Play();
                    }
                }
                foreach (var audioSource in extraAudioSources) {
                    if (audioSource.enabled && audioSource.gameObject.activeInHierarchy) {
                        audioSource.Play();
                    }
                }
            }
            else if (!play && isPlaying) {
                isPlaying = false;
                foreach (var audioSource in audioSources) {
                    if (audioSource.enabled && audioSource.gameObject.activeInHierarchy) {
                        audioSource.Stop();
                    }
                }
                foreach (var audioSource in extraAudioSources) {
                    if (audioSource.enabled && audioSource.gameObject.activeInHierarchy) {
                        audioSource.Stop();
                    }
                }
            }
            return isPlaying;
        }

        public static bool ToggleAudio(bool play, bool isPlaying, params AudioSource[] audioSources) {
            if (play && !isPlaying) {
                isPlaying = true;
                foreach (var audioSource in audioSources) {
                    audioSource.Play();
                }
            }
            else if (!play && isPlaying) {
                isPlaying = false;
                foreach (var audioSource in audioSources) {
                    audioSource.Stop();
                }
            }
            return isPlaying;
        }

    }
}
